from ConfigParser import ConfigParser
from common import InferenceEngine
from urllib2 import HTTPError
from rdflib.syntax.serializers.XMLSerializer import XMLSerializer
import sys
import logging

import urllib
import urllib2
import urlparse

import libxml2
import simplejson

from rdflib.Graph import ConjunctiveGraph, Graph, ReadOnlyGraphAggregate
from rdflib import URIRef, Literal, BNode, Namespace, StringInputSource
from rdflib import RDF, RDFS

from pyxmpp.all import JID,Iq,Presence,Message,StreamError
from pyxmpp.jabber.client import JabberClient
from pyxmpp.interface import implements
from pyxmpp.interfaces import *

from common import UCI_NS, AGENT_NS, ECP_NS


#class Libxml2Serializer(XMLSerializer):
#    def __init__(self, store):
#        super(XMLSerializer, self).__init__(store)
#
#    def __bindings(self):
#        from rdflib.util import uniq
#        store = self.store
#        nm = store.namespace_manager
#        bindings = {}
#        for predicate in uniq(store.predicates()):
#            prefix, namespace, name = nm.compute_qname(predicate)
#            bindings[prefix] = URIRef(namespace)
#        RDFNS = URIRef("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
#        if "rdf" in bindings:
#            assert bindings["rdf"]==RDFNS
#        else:
#            bindings["rdf"] = RDFNS
#        for prefix, namespace in bindings.iteritems():
#            yield prefix, namespace
#
#    def serialize(self):
#        #self.base = base
#        doc = libxml2.newDoc('1.0')
#        doc.createIntSubset('RDF', None, None)
#        
#        bindings = list(self.__bindings())
#        bindings.sort()
#        
#        root = doc.newChild(None, 'RDF', None)
#        
#        self.prefix_to_ns_map = prefix_to_ns_map = {}
#        self.entity_ns_map = entity_ns_map = {}
#        
#        for prefix, namespace in bindings:
#            if prefix:
#                entity_name = '%s_ns' % prefix
#                entity_ref = '&%s;' % entity_name
#                doc.addDocEntity(entity_name, libxml2.XML_INTERNAL_GENERAL_ENTITY, None, None, namespace)
#                entity_ns_map[namespace] = entity_ref
#                ns = root.newNs(entity_ref, prefix)
#                prefix_to_ns_map[prefix] = ns
#            else:
#                ns = root.newNs(namespace, None)
#                
#        root.setNs(prefix_to_ns_map['rdf'])
#        
#        # write out triples by subject
#        for subject in self.store.subjects():
#            self.subject(doc, subject, 1)
#        
#        del self.__serialized
#
#        return doc
#
#
#    def subject(self, subject, depth=1):
#        if not subject in self.__serialized:
#            self.__serialized[subject] = 1
#            if isinstance(subject, (BNode,URIRef)):
#                write = self.write
#                indent = "  " * depth
#                element_name = "rdf:Description"
#                if isinstance(subject, BNode):
#                    write( '%s<%s rdf:nodeID="%s"' %
#                       (indent, element_name, subject))
#                else:
#                    uri = quoteattr(self.relativize(subject))
#                    write( "%s<%s rdf:about=%s" % (indent, element_name, uri))
#                if (subject, None, None) in self.store:
#                    write( ">\n" )
#                    for predicate, object in self.store.predicate_objects(subject):
#                        self.predicate(predicate, object, depth+1)
#                    write( "%s</%s>\n" % (indent, element_name))
#                else:
#                    write( "/>\n" )
#
#    def predicate(self, predicate, object, depth=1):
#        write = self.write
#        indent = "  " * depth
#        qname = self.store.namespace_manager.qname(predicate)
#        if isinstance(object, Literal):
#            attributes = ""
#            if object.language:
#                attributes += ' xml:lang="%s"'%object.language
#
#            if object.datatype:
#                attributes += ' rdf:datatype="%s"'%object.datatype
#
#            write("%s<%s%s>%s</%s>\n" %
#                  (indent, qname, attributes,
#                   escape(object), qname) )
#        else:
#            if isinstance(object, BNode):
#                write('%s<%s rdf:nodeID="%s"/>\n' %
#                      (indent, qname, object))
#            else:
#                write("%s<%s rdf:resource=%s/>\n" %
#                      (indent, qname, quoteattr(self.relativize(object))))


class UciRpcHandler(object):
    """Provides handler for a version query.
    
    This class will answer version query and announce 'jabber:iq:version' namespace
    in the client's disco#info results."""
    
    implements(IIqHandlersProvider, IFeaturesProvider)

    def __init__(self, client):
        """Just remember who created this."""
        self.client = client
        self.plugins = client.plugins
        self.uci_graph = Graph()
        self.uci_graph.parse("uci.owl")
        self.agent_graph = Graph()
        self.agent_graph.load("agent.owl")
        self.is_initialized = False
        
    def get_features(self):
        """Return namespace which should the client include in its reply to a
        disco#info query."""
        return ["uci:rpc"]

    def get_iq_get_handlers(self):
        """Return list of tuples (element_name, namespace, handler) describing
        handlers of <iq type='get'/> stanzas"""
        return []

    def get_iq_set_handlers(self):
        """Return list of tuples (element_name, namespace, handler) describing
        handlers of <iq type='set'/> stanzas"""
        return [
            ("request", "uci:rpc", self.uci_rpc),
        ]
    
    def uci_rpc(self, iq):
        print "Got UCI request"
        
        if not self.is_initialized:
            self.is_initialized = True
            print "Initializing plugins"
            for p in self.plugins:
                p.initialize()
        
        # Parse request
        query = iq.get_query()
        
        req_graph = self._parse_from_libxml_node(query.children)
        e = InferenceEngine(ReadOnlyGraphAggregate([self.uci_graph, self.agent_graph, req_graph]))

        #print req_graph.serialize()

        # Analyze request
        try:
            requests = e.get_instances_of(AGENT_NS['AgentRequest'])
            assert len(requests) == 1, "Request RDF must contain exactly one instance of AgentRequest"
            
            req_id = requests[0]
            req_type = req_graph.value(req_id, RDF.type)
            print "%s: %s" % (req_id, req_type)
        
            if req_type == AGENT_NS['GetStateRequest']:
                result = Graph()
                for p in self.plugins:
                    g = p.get_state(req_id, req_graph)
                    result += g
                response_uri = BNode()
                result.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
                
            elif req_type == AGENT_NS['StartVirtualMachineRequest']:
                machine_uri = req_graph.value(subject=req_id, predicate=AGENT_NS['virtual_machine'])
                p = self._select_plugin_for(machine_uri)
                result = p.start_instance(req_id, req_graph)
                response_uri = BNode()
                result.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
            elif req_type == AGENT_NS['ShutdownVirtualMachineRequest']:
                machine_uri = req_graph.value(subject=req_id, predicate=AGENT_NS['virtual_machine'])
                p = self._select_plugin_for(machine_uri)
                result = p.stop_instance(req_id, req_graph)
                response_uri = BNode()
                result.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
            elif req_type == AGENT_NS['DestroyVirtualMachineRequest']:
                machine_uri = req_graph.value(subject=req_id, predicate=AGENT_NS['virtual_machine'])
                p = self._select_plugin_for(machine_uri)                
                result = p.destroy(req_id, req_graph)
                response_uri = BNode()
                result.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
            elif req_type == AGENT_NS['ProvisionRequest']:
                cloud = req_graph.value(req_id, AGENT_NS['cloud'])
                p = self._select_plugin_for(cloud)                
                result = p.provision(req_id, req_graph)
                response_uri = BNode()
                result.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
            else:
                print "Unknown request type: %s" % req_type
                result = self._make_error(AGENT_NS['MalformedRequestError'], unicode(e))
                
        except NotImplementedError, e:
            result = self._make_error(AGENT_NS['NotImplementedError'], unicode(e))
            import traceback
            traceback.print_exc()
        except Exception, e:
            result = self._make_error(AGENT_NS['InternalAgentError'], unicode(e))
            import traceback
            traceback.print_exc()
        
        
        res_xml = result.serialize(format="xml")
        print "Sending reply (%d bytes)" % len(res_xml)
        
        #print result.serialize(format="n3")
        result_iq = iq.make_result_response()
        result_iq.add_content(self._serialize_as_libxml_node(result))

        return result_iq
    
    def _select_plugin_for(self, uri):
        avail = [p for p in self.plugins if p.is_uri_from_this_plugin(uri)]
        assert len(avail) in [0, 1]
        
        if len(avail) == 0:
            raise Exception('Unable to determine URIRef source')
        return avail[0]
#
#    def _startinstance_handler(self, req_id, graph):
#        machine_id = graph.value(subject=req_id, predicate=AGENT_NS['virtual_machine'])
#        
#        machine_uuid = machine_map[machine_id]
#        
#        print "Starting %s (%s)" % (machine_id, machine_uuid)
#        
#        ecp_uri = urlparse.urljoin(self.client.ecp_base, 'rest/machine/%s/actions/create/' % machine_uuid)
#        print ecp_uri
#        json_response = self._ecp_request(ecp_uri, '').read()
#        response = simplejson.loads(json_response)
#        
#        print "got ECP response: %s" % response
#        
#        g = Graph()
#        response_uri = BNode()
#        g.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
#        return g   
#
#    def _shutdowninstance_handler(self, req_id, graph):
#        machine_id = graph.value(subject=req_id, predicate=AGENT_NS['virtual_machine'])
#        
#        machine_uuid = machine_map[machine_id]
#        
#        print "Stopping %s (%s)" % (machine_id, machine_uuid)
#        
#        ecp_uri = urlparse.urljoin(self.client.ecp_base, 'rest/machine/%s/actions/shutdown/' % machine_uuid)
#        print ecp_uri
#        json_response = self._ecp_request(ecp_uri, '').read()
#        response = simplejson.loads(json_response)
#        
#        print "got ECP response: %s" % response
#        
#        g = Graph()
#        response_uri = BNode()
#        g.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
#        return g   
#
#    def _destroyinstance_handler(self, req_id, graph):
#        machine_id = graph.value(subject=req_id, predicate=AGENT_NS['virtual_machine'])
#        
#        machine_uuid = machine_map[machine_id]
#        
#        print "Shutdown %s (%s)" % (machine_id, machine_uuid)
#        
#        ecp_uri = urlparse.urljoin(self.client.ecp_base, 'rest/machine/%s/actions/totaldelete/' % machine_uuid)
#        json_response = self._ecp_request(ecp_uri, '').read()
#        response = simplejson.loads(json_response)
#        
#        print "got ECP response: %s" % response
#        
#        g = Graph()
#        response_uri = BNode()
#        g.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
#        return g   
#    
#    def _provision_handler(self, req_id, graph):
#        provision_to = graph.value(subject=req_id, predicate=AGENT_NS['provision_to'])
#        package = graph.value(subject=req_id, predicate=AGENT_NS['package'])
#        
#        raise NotImplementedError("Provisioning is not implemented for ECP")
#
#    def _getstate_handler(self, req_id, req):
#        
#        machines = [machine_map[x] for x in req.objects(req_id, AGENT_NS['infrastructure_elements'])]
#        
#        if not machines:
#            ecp_uri = urlparse.urljoin(self.client.ecp_base, 'rest/machines/')
#            json_response = self._ecp_request(ecp_uri).read()
#            response = simplejson.loads(json_response)
#            machines = response['machines']
#
#        g = Graph()        
#        
#        response_uri = BNode()
#        g.add((response_uri, RDF.type, AGENT_NS['SuccessfulResponse']))
#        
#        cloud = CLOUDS_NS['ecp_cloud']
#        
#        g.add((cloud, RDF.type, ECP_NS['ECPCloud']))
#        g.add((cloud, RDFS.label, Literal("ECP instance at %s" % self.client.ecp_base)))
#
#        for mid in machines:
#            machine = MACHINES_NS[mid]
#
#            # Store reverse mapping: resource_id -> machine_uuid
#            machine_map[machine] = mid
#
#            g.add((machine, RDF.type, ECP_NS['ECPVirtualMachine']))
#            g.add((machine, UCI_NS['belongs_to_cloud'], cloud))
#
#            ecp_uri = urlparse.urljoin(self.client.ecp_base, 'rest/machine/%s/' % mid)
#            json_response = self._ecp_request(ecp_uri).read()
#            response = simplejson.loads(json_response)
#            
#            errno = int(response['errno'])
#            if errno == 0:
#                m = response['machine']
#                info = response['info']
#                
#                g.add((machine, RDFS.label, Literal(m['machine_name'])))
#                
#                if info[0] in (1, 2):
#                    g.add((machine, UCI_NS['machine_state'], UCI_NS['machine_running']))
#                elif info[0] in (3, 4, 5, 6):
#                    g.add((machine, UCI_NS['machine_state'], UCI_NS['machine_stopped']))
#         
#        
#        # fetch packages
#        ecp_uri = urlparse.urljoin(self.client.ecp_base, 'rest/valet/packages/')
#        json_response = self._ecp_request(ecp_uri).read()
#        response = simplejson.loads(json_response)
#        packages = response['packages']
#        
#        for uuid, name in packages:
#            package = PACKAGE_NS[uuid]
#            g.add((package, RDF.type, UCI_NS['VirtualMachinePackage']))        
#            g.add((package, RDFS.label, Literal(name)))  
#            g.add((package, UCI_NS['belongs_to_cloud'], cloud))      
#         
#                        
#        return g

    def _make_success(self):
        pass

    def _make_error(self, error_type, message):
        result = Graph()
        resp = BNode()
        result.add((resp, RDF.type, error_type))
        result.add((resp, RDFS.comment, Literal(message)))
        return result
                
    def _parse_from_libxml_node(self, node):
        g = Graph()
        g.parse(StringInputSource(node.serialize()))
        return g
        
    def _serialize_as_libxml_node(self, graph):
        return libxml2.parseDoc(graph.serialize(format="xml")).getRootElement()

    
class Client(JabberClient):
    def __init__(self, jid, password, server=None, port=5222, plugins=[]):
        self.plugins = plugins
        # if bare JID is provided add a resource -- it is required
        if not jid.resource:
            jid=JID(jid.node, jid.domain, "uci_agent")

        # setup client with provided connection information
        # and identity data
        JabberClient.__init__(self, jid, password, server, port,
                disco_name="Unified Cloud Initiative Agent", disco_type="bot")

        # add the separate components
        self.interface_providers = [
            UciRpcHandler(self),
        ]
        
    def stream_state_changed(self,state,arg):
        """This one is called when the state of stream connecting the component
        to a server changes. This will usually be used to let the user
        know what is going on."""
        print "*** State changed: %s %r ***" % (state,arg)
        
    def session_started(self):
        JabberClient.session_started(self)
        print "Agent ready"
        
## XMPP protocol is Unicode-based to properly display data received
## _must_ convert it to local encoding or UnicodeException may be raised
#locale.setlocale(locale.LC_CTYPE, "")
#encoding = locale.getlocale()[1]
#if not encoding:
#    encoding = "utf-8"
#sys.stdout = codecs.getwriter(encoding)(sys.stdout, errors = "replace")
#sys.stderr = codecs.getwriter(encoding)(sys.stderr, errors = "replace")


# PyXMPP uses `logging` module for its debug output
# applications should set it up as needed
logger = logging.getLogger()
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.INFO) # change to DEBUG for higher verbosity

if len(sys.argv) < 2:
    print u"Usage:"
    print "\t%s config_file" % (sys.argv[0],)
#    print "example:"
#    print "\t%s test@localhost verysecret" % (sys.argv[0],)
    sys.exit(1)

config = ConfigParser()
config.read(sys.argv[1])
if not config.has_section('uci_agent'):
    print "Config file does not have 'uci_agent' section"
    sys.exit(1)

print u"creating client..."

from plugins.ecp_plugin import Plugin as ECPPlugin
from plugins.ec2_plugin import Plugin as EC2Plugin

plugin_type_map = {'ecp': ECPPlugin, 'ec2': EC2Plugin}

plugins = []
for section in config.get('uci_agent', 'plugins').split(','):
    section = section.strip()
    ptype = config.get(section, 'type')
    p = plugin_type_map.get(ptype, None)
    if p is None:
        print "Unknown plugin type '%s'. Skipping." % ptype
    plugins.append(p(config, section))

server = None
if config.has_option('uci_agent', 'server'):
    server = config.get('uci_agent', 'server')

c=Client(JID(config.get('uci_agent', 'jid')), 
         config.get('uci_agent', 'password'),
         server,
         plugins=plugins)

print u"connecting..."
c.connect()

print u"looping..."
try:
    # Component class provides basic "main loop" for the applitation
    # Though, most applications would need to have their own loop and call
    # component.stream.loop_iter() from it whenever an event on
    # component.stream.fileno() occurs.
    c.loop(1)
except KeyboardInterrupt:
    print u"disconnecting..."
    c.disconnect()

print u"exiting..."
